<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    /* 
    通过Object.defineProperty为对象obj添加属性，可以设置对象属性的getter和setter函数。
    之后我们每次通过点语法获取属性都会执行这里的getter函数，在这个函数中我们会把调用此属性的依赖收集到一个集合中 ；
    而在我们给属性赋值(修改属性)时，会触发这里定义的setter函数，在次函数中会去通知集合中的依赖更新，做到数据变更驱动视图变更。
    */

    // 封装Vue 实现数据代理
    function Vue ({ data }) {
      // 在vue内部 把数据放在 vm._data上
      this._data = data

      /* 
      在模板中是直接从vm上获取数据  用_data.xxx获取数据不方便
      所以在vm上放一份数据实现数据代理 代理_data上的数据
      */
      for (let key in this._data) {
        //给已经存在的实例对象vm 扩展一些属性,并且设置存取器属性,需要使用Object.defineProperty
        Object.defineProperty(this, key, {
          get: function proxyGettter () {
            //当被读取的时候,去_data中获取值
            return this._data[key]
          },
          set: function proxySettter (value) {
            this._data[key] = val
          }
        })
      }
      // 进行数据劫持
      observe(this._data);

    }

    //封装一个函数,专门用来做数据劫持的(可以认为是发布订阅模型中的发布者)
    function observe (data) {
      if (typeof data !== "object" || data === null) return;


      //实例化一个发布者
      new Observer(data);

    }


    //我们封装一个Observer类(创建发布者--数据劫持)
    class Observer {
      constructor(data) {
        this.data = data;
        //判断当前需要被劫持的值是数组还是对象,然后分发到不同的方法中
        if (Array.isArray(this.data)) {
          this.observeArray(this.data);
        } else {
          this.walk(this.data);
        }
      }

      //发布者Observer类上 处理数组格式的劫持方法
      observeArray (data) {
        //当前还是要遍历一下数组,如果数组的值是对象,还要对这个对象进行劫持,
        //如果数组的值是基本类型则不需要任何处理,因为vue在其他位置已经对变更方法封装了
        data.forEach((item) => {
          observe(item);
        });
      }

      //发布者Observer类上 处理对象格式的劫持方法
      walk (data) {
        //遍历当前的对象,对对象内部的属性一一做一个劫持
        for (let key in data) {
          defineReactive(data, key);
        }
      }
    }





    //只是把observer中的数据劫持功能功能提炼出来了
    function defineReactive (data, key) {
      let value = data[key];

      


      //每次要开始数据劫持之前,我们就会对某个被劫持的属性创建一个 dep(中间人),
      //这个dep可以收集当前数据的所有依赖项,并且还拥有了通知所有依赖项更新的方法
      let dep = new Dep();

      //调用observe方法,判断当前value值是不是一个对象或者数据来进行深层次的劫持
      observe(value);



      Object.defineProperty(data, key, {
        get: function reactiveGetter () {

          //只有模板收集数据的数据的时候,这个模板才是依赖项(订阅者),我们才会收集这个依赖,
          //所以我们要先进行判断 然后再收集依赖
          dep.depend("xxxxx");


          return value;
        },
        set: function reactiveSetter (newVal) {
          if (newVal === value) return;
          //否则给value赋值,当再次取值的时候,得到就是最新的value
          value = newVal;
          //通知模板重新获取数据
          console.log("值的修改被劫持了,模板赶紧重新获取值,然后更新");

          //调用当前数据对应的dep的notify方法,通知模板更新
          dep.notify();
        },
      });
    }


    //封装一个 发布订阅中心(中间人)的类
    class Dep {
      constructor() {
        //每次实例化一个中间人dep的时候,就要创建一个新的数组用来收集所有的依赖(订阅者)
        this.sub = [];
      }

      //中间人必须存在的方法1:收集依赖
      depend (watcher) {
        this.sub.push(watcher);
      }

      //中间人必须存在的方法2:通知所有的依赖更新
      notify () { }
    }


















    // 创建vm实例
    const vm = new Vue({
      data: {
        count: 15,
        user: {
          name: "theshy",
          age: 18,
        },
        course: ["html", "js", "css"],
        person: [
          { id: "001", name: "theshy" },
          { id: "002", name: "小李" },
        ],
      },
    });

    console.log(vm);


      //初始化的时候要获取一次数据



  </script>
</body>

</html>